/*
 * xforce-hw.c
 *
 * This plugin implements the canonical first program.  In this case, we will
 * create a window that has the text hello-world in it.  As an added bonus
 * the  text will change to 'This is a plugin' while the mouse is held down
 * in the window.
 *
 * This plugin demonstrates creating a window and writing mouse and drawing
 * callbacks for that window.
 *
 */

#include <stdio.h>
#include <string.h>
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMMenus.h"
#include "XPLMUtilities.h"
#include "XPWidgets.h"
#include "XPStandardWidgets.h"
#include "XPUIGraphics.h"
#include "XPLMDataAccess.h"
#include "XPLMProcessing.h"


#include "XPWidgetsEx.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/input.h>

#include <sstream>
#include <iostream>

#include "OIS.h"

#define PLUGIN_NAME "xforce-hw"

using namespace OIS;

class GUI;

GUI *g_GUI;

InputManager *g_InputManager = 0;

typedef std::vector<JoyStick *> JoyList;

JoyList g_joys;

ForceFeedback *g_ff;

void menuHandler(void *, void *);
int mainWidgetHandler(XPWidgetMessage inMessage, XPWidgetID inWidget, long inParam1, long inParam2);
void buildDeviceList();
float MyFlightLoopCallback(float inElapsedSinceLastCall, float inElapsedTimeSinceLastFlightLoop, int inCounter, void *inRefcon);


class GUI
{
	private:
		XPLMMenuID menuId;
		XPWidgetID devicePopup;
		XPWidgetID devicePopupInfoText;
		char szPopupText[4096];

	protected:
		XPWidgetID mainWidget;

	private:
		void createMenu() {
			int item = XPLMAppendMenuItem(XPLMFindPluginsMenu(), PLUGIN_NAME, NULL, 1);

			menuId = XPLMCreateMenu(PLUGIN_NAME, XPLMFindPluginsMenu(), item, menuHandler, NULL);

			XPLMAppendMenuItem(menuId, "Configure", (void *)"Configure", 1);
		}

		void destroyMenu() {
			if (mainWidget) {
				XPDestroyWidget(mainWidget, 1);
			}

			XPLMDestroyMenu(menuId);
		}

	protected:
		void createWidgets(int x, int y, int w, int h)
		{
			int x2 = x + w;
			int y2 = y - h;
			char windowTitleBuf[256];

			buildDeviceList();

			windowTitleBuf[0] = '\0';
			strcat(windowTitleBuf, PLUGIN_NAME);
			strcat(windowTitleBuf, " Configuration");

			mainWidget = XPCreateWidget(x, y, x2, y2,
							1,	// Visible
							windowTitleBuf,	// desc
							1,		// root
							NULL,	// no container
							xpWidgetClass_MainWindow);
			XPSetWidgetProperty(mainWidget, xpProperty_MainWindowHasCloseBoxes, 1);

			XPWidgetID mainWindow = XPCreateWidget(x + 50, y - 40, x2 - 50, y2 + 30,
							1,	// Visible
							"",	// desc
							0,		// root
							mainWidget,
							xpWidgetClass_SubWindow);
			XPSetWidgetProperty(mainWindow, xpProperty_SubWindowType, xpSubWindowStyle_SubWindow);

			for(JoyList::iterator i = g_joys.begin(); i != g_joys.end(); i++ )
			{
				std::ostringstream joyName;
				joyName << *i << " ";

				strcat(szPopupText, joyName.str().c_str());
				strcat(szPopupText, ";" );
			}

			devicePopup = XPCreatePopup(x + 70, y - 50, x2 - 70, y - 72, 1, szPopupText, mainWidget);

			devicePopupInfoText = XPCreateWidget(x+70, y-80, x2-70, y-102,
									1, "Popup Index 0", 0, mainWidget,
									xpWidgetClass_TextField);

			XPSetWidgetProperty(devicePopupInfoText, xpProperty_TextFieldType, xpTextEntryField);

		/*	PopupSetIndexButton = XPCreateWidget(x+70, y-110, x+170, y-132,
							1, " Set Popup Index", 0, mainWidget,
							xpWidgetClass_Button);

			XPSetWidgetProperty(PopupSetIndexButton, xpProperty_ButtonType, xpPushButton);

			PopupInputTextEdit = XPCreateWidget(x+180, y-110, x2-70, y-132,
									1, "0", 0, mainWidget,
									xpWidgetClass_TextField);

			XPSetWidgetProperty(PopupInputTextEdit, xpProperty_TextFieldType, xpTextEntryField);

			TestWidgetsListBox = XPCreateListBox( x+70, y-140, x2-70, y-340,
												 1, szListBoxText, mainWidget );
		*/

			XPAddWidgetCallback(mainWidget, mainWidgetHandler);
		}

	public:

		static int getSystemWindowId()
		{
			return XPLMGetDatai(XPLMFindDataRef("sim/operation/windows/system_window"));
		}

		GUI() {
			resetDevicePopupText();

			createMenu();
		}

		~GUI() {
			destroyMenu();
		}

		void show() {
			if (!mainWidget)
			{
				createWidgets(300, 650, 450, 600);
			}
			else {
				if(!XPIsWidgetVisible(mainWidget))
					XPShowWidget(mainWidget);
			}
		}

		void hide() {
			if (mainWidget) {
				XPHideWidget(mainWidget);
			}
		}

		void resetDevicePopupText() {
			szPopupText[0] = '\0';
		}

		void setDevicePopupInfoText(char *text) {
			XPSetWidgetDescriptor(devicePopupInfoText, text);
		}

		XPWidgetID getDevicePopupWidget() {
			return devicePopup;
		}
};

/*
 * XPluginStart
 *
 * Our start routine registers our window and does any other initialization we
 * must do.
 *
 */
PLUGIN_API int XPluginStart(char *outName, char *outSig, char *outDesc)
{
	strcpy(outName, PLUGIN_NAME);
	strcpy(outSig, "lobz.forcefeedback.");
	strcat(outSig, PLUGIN_NAME);
	strcpy(outDesc, "A plugin that makes the force :)");

	g_ff = NULL;

	{ // create the input manager
		ParamList pl;

		std::ostringstream wnd;
		wnd << GUI::getSystemWindowId();

		pl.insert(std::make_pair(std::string("WINDOW"), wnd.str()));

		g_InputManager = InputManager::createInputSystem(pl);

		InputManager &im = *g_InputManager;
		std::cout << "Input Manager (" << im.inputSystemName() << ") Created.." << std::endl;
	}

	g_GUI = new GUI();

	/* We must return 1 to indicate successful initialization, otherwise we
	 * will not be called back again. */

	return 1;
}

/*
 * XPluginStop
 *
 * Our cleanup routine deallocates our window.
 *
 */
PLUGIN_API void XPluginStop(void)
{
	// Clean up

	XPLMUnregisterFlightLoopCallback(MyFlightLoopCallback, NULL);

	delete g_GUI;

	for(JoyList::iterator i = g_joys.begin(); i != g_joys.end(); i++ )
	{
		g_InputManager->destroyInputObject(*i);
	}

	g_joys.clear();

	InputManager::destroyInputSystem(g_InputManager);

	g_ff = NULL;
}

/*
 * XPluginDisable
 *
 * We do not need to do anything when we are disabled, but we must provide the handler.
 *
 */
PLUGIN_API void XPluginDisable(void)
{
}

/*
 * XPluginEnable.
 *
 * We don't do any enable-specific initialization, but we must return 1 to indicate
 * that we may be enabled at this time.
 *
 */
PLUGIN_API int XPluginEnable(void)
{
	return 1;
}

/*
 * XPluginReceiveMessage
 *
 * We don't have to do anything in our receive message handler, but we must provide one.
 *
 */
PLUGIN_API void XPluginReceiveMessage(
					XPLMPluginID	inFromWho,
					long			inMessage,
					void *			inParam)
{
}

void menuHandler(void *mRef, void *iRef) {
	if (!strcmp((char *) iRef, "Configure"))
	{
		g_GUI->show();
	}
}

// Handle any widget messages
int mainWidgetHandler(XPWidgetMessage inMessage, XPWidgetID inWidget, long inParam1, long inParam2) {
	char Buffer[256];

	// Close button pressed, only hide the widget, rather than destropying it.
	if (inMessage == xpMessage_CloseButtonPushed)
	{
		g_GUI->hide();

		return 1;
	}

	// This handles the popup item picked message
	// i.e. when you selecte another popup entry.
	if (inMessage == xpMessage_PopupNewItemPicked)
	{
		XPGetWidgetDescriptor(g_GUI->getDevicePopupWidget(), Buffer, sizeof(Buffer));
		int curItem = XPGetWidgetProperty(g_GUI->getDevicePopupWidget(), xpProperty_PopupCurrentItem, NULL);
		sprintf(Buffer, "Popup Index %d", curItem);

		g_ff = (ForceFeedback*)g_joys[curItem]->queryInterface(Interface::ForceFeedback);

		g_ff->setAutoCenterMode(false);

		XPLMRegisterFlightLoopCallback(MyFlightLoopCallback, 1.0, NULL);

//		shutdownDevice(selectedDevice);

//		selectedDevice = &deviceList[curItem];

//		resetDevice(selectedDevice);
		g_GUI->setDevicePopupInfoText(Buffer);

		return 1;
	}

	return 0;
}


float MyFlightLoopCallback(float inElapsedSinceLastCall, float inElapsedTimeSinceLastFlightLoop, int inCounter, void *inRefcon)
{
	static Effect *cf = NULL;
	static Effect *rf = NULL;

	static float ftireforce = 0;

	if (!g_ff) {
		return 0;
	}

	XPLMDataRef dr_ff_hydraulic = XPLMFindDataRef("sim/aircraft/forcefeedback/acf_ff_hydraulic");
	XPLMDataRef dr_ff_stickshaker = XPLMFindDataRef("sim/aircraft/forcefeedback/acf_ff_stickshaker");

	XPLMDataRef dr_faxil_gear = XPLMFindDataRef("sim/flightmodel/forces/faxil_gear");
	XPLMDataRef dr_fside_gear = XPLMFindDataRef("sim/flightmodel/forces/fside_gear");
	XPLMDataRef dr_fnrml_gear = XPLMFindDataRef("sim/flightmodel/forces/fnrml_gear");

	XPLMDataRef dr_tire_vertical_force_n_mtr = XPLMFindDataRef("sim/flightmodel2/gear/tire_vertical_force_n_mtr");
	XPLMDataRef dr_tire_rotation_speed_rad_sec = XPLMFindDataRef("sim/flightmodel2/gear/tire_rotation_speed_rad_sec");


	float fval = 0;

	for (int i = 0; i < 3; i++) {
		XPLMGetDatavf(dr_tire_vertical_force_n_mtr, &fval, i, 1);
		std::cout << "dr_tire_vertical_force_n_mtr[" << i << "]: " << fval << std::endl;
	}

	for (int i = 0; i < 3; i++) {
		XPLMGetDatavf(dr_tire_rotation_speed_rad_sec, &fval, i, 1);
		std::cout << "dr_tire_rotation_speed_rad_sec[" << i << "]: " << fval << std::endl;
	}

	for (int i = 0; i < 3; i++) {
		XPLMGetDatavf(dr_faxil_gear, &fval, i, 1);
		std::cout << "faxil_gear[" << i << "]: " << fval << std::endl;
	}

	float fnewtireforce = 0;
	XPLMGetDatavf(dr_tire_vertical_force_n_mtr, &fnewtireforce, 0, 1);
//	float ftirerotation = 0;
//	XPLMGetDatavf(dr_tire_rotation_speed_rad_sec, &ftirerotation, 0, 1);

	float dforce = fnewtireforce - ftireforce;

	ftireforce = fnewtireforce;

	if (!cf) {
		cf = new Effect(Effect::ConstantForce, Effect::Constant);
	}

	if (!rf) {
//		rf = new Effect(Effect::RumbleForce, Effect::Rumble);
	}

	  static Effect* pEffect;
	  ConstantEffect* pConstForce;
	  PeriodicEffect* pPeriodForce;

	if (!pEffect) {
	  pEffect = new Effect(Effect::PeriodicForce, Effect::Square);
	}


	if (dforce > 1000) {
		std::cout << "DFORCE: " << dforce << std::endl << std::endl;

	  pEffect->direction = Effect::North;
	  pEffect->trigger_button = 0;
	  pEffect->trigger_interval = 0;
//	  pEffect->replay_length = Effect::OIS_INFINITE;
	  pEffect->replay_length = 0;
	  pEffect->replay_delay = 0;
	  pEffect->setNumAxes(1);
	  pPeriodForce = dynamic_cast<PeriodicEffect*>(pEffect->getForceEffect());
	  pPeriodForce->magnitude = dforce;  // 0 to +10k
	  pPeriodForce->offset = 0;
	  pPeriodForce->phase = 0;  // 0 to 35599
	  pPeriodForce->period = 50;  // Micro-seconds
	  pPeriodForce->envelope.attackLength = 0;
	  pPeriodForce->envelope.attackLevel = (unsigned short)pPeriodForce->magnitude;
	  pPeriodForce->envelope.fadeLength = 0;
	  pPeriodForce->envelope.fadeLevel = (unsigned short)pPeriodForce->magnitude;

//		static_cast<RumbleEffect*>(rf->getForceEffect())->strongMagnitude = 1000;
//		static_cast<RumbleEffect*>(rf->getForceEffect())->weakMagnitude = 1000;
//		rf->replay_length = 100;

		g_ff->upload(pEffect);
	}

//	static_cast<ConstantEffect*>(cf->getForceEffect())->level = ftireforce * ftirerotation;


	return 0.5;
}



void buildDeviceList() {
	InputManager &im = *g_InputManager;

	int numSticks = im.getNumberOfDevices(OISJoyStick);
	bool ffFound = false;

	for( int i = 0; i < numSticks; i++ )
	{
		//Create the stick
		JoyStick *joy = (JoyStick*)im.createInputObject( OISJoyStick, true );

		//Check for FF, if so, dump info
		ForceFeedback *ff = (ForceFeedback *)joy->queryInterface( Interface::ForceFeedback );
		if (ff)
		{
			ffFound = true;
			std::cout << "Created buffered joystick **with ForceFeedback** support.\n";
			const ForceFeedback::SupportedEffectList &list = ff->getSupportedEffects();
			ForceFeedback::SupportedEffectList::const_iterator i = list.begin(),
				e = list.end();
			for( ; i != e; ++i)
				std::cout << "Force =  " << i->first << " Type = " << i->second << std::endl;

			g_joys.push_back(joy);
		}
		else {
			std::cout << "Joystick **without** FF support\n";
		}
	}
}
